package extend.db.shard;

import extend.db.Builder;
import extend.db.base.Query;
import extend.db.base.SqlMap;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 分表,分库工具类
 * <B>说明：</B>
 * <pre>
 *  略
 * </pre>
 * <B>示例：</B>
 * <pre>
 *  略
 * </pre>
 * <B>日志：</B>
 * <pre>
 *  略
 * </pre>
 * <B>注意事项：</B>
 * <pre>
 *  略
 * </pre>
 */
public class ShardUtil
{

    // 分表规则默认方法名称
    protected static final String TABLE_SHARD_METHOD_NAME = "tableRule";

    // 分库规则默认方法名称
    protected static final String DATABASE_SHARD__METHOD_NAME = "dbRule";


    /**
     * 格式化插入数据
     *<B>说明：</B>
     *<pre>
     *  query 按分表规则多个Query 对象,每张表对应一个Query
     *</pre>
     */
    public static List<Query> parseQueryDataForTable(Query query,Class clazz)
    {

        List<Query> querysList;
        // 未设置分表规则
        ShardRule tableRule = callShardMethod(TABLE_SHARD_METHOD_NAME,clazz);
        if (tableRule == null) {
            querysList = new ArrayList<>();
            querysList.add(query);
        } else {
            Map<String,Query> querys = new HashMap<>();

            for (Map<String,Object> data:query.getData()) {
                Map<String,Object> values = getShardData(tableRule,data);
                String shardTableSequence = tableRule.getSequence(query,values);
                String shardTable = buildTableName(query.getTable(),shardTableSequence);
                if (!querys.containsKey(shardTable)) {
                    querys.put(shardTable,query.cloneQuery(new HashMap<String,Object>(){{
                        put(Query.TABLE_SET,shardTable);
                        put(Query.DATA_SET,null);
                    }}));
                }

                querys.get(shardTable).setData(data);
            }

            querysList = new ArrayList(querys.values());
        }

        return querysList;
    }

    /**
     * 分表情况下解析Where
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public static List<Query> parseQueryWhereForTable(Query query,Class clazz)
    {
        List<Query> querysList;

        ShardRule tableRule = callShardMethod(TABLE_SHARD_METHOD_NAME,clazz);
        // 未设置分表规则
        if (tableRule == null) {
            querysList = new ArrayList<>();
            querysList.add(query);
        } else {
            Map<String,Query> querys = new HashMap<>();
            // 分表规则where 条件值
            Map<String,Object> ruleWhere = getShardData(tableRule,query.getWhere());
            Map<String,Map<String,Object>> sequenceWhere = formatShardRuleWhere(ruleWhere,tableRule,query);

            for (Map.Entry<String,Map<String,Object>> entry : sequenceWhere.entrySet()) {
                String shardTable = buildTableName(query.getTable(),entry.getKey());
                if (!querys.containsKey(shardTable)) {
                    Map<String,Object> where = SqlMap.make();
                    where.putAll(query.getWhere());
                    where.putAll(entry.getValue());
                    querys.put(shardTable,query.cloneQuery(new HashMap<String,Object>(){{
                        put(Query.TABLE_SET,shardTable);
                        put(Query.WHERE_SET,where);
                    }}));
                }
            }

            querysList = new ArrayList(querys.values());
        }

        return querysList;
    }

    /**
     * 格式化插入数据
     *<B>说明：</B>
     *<pre>
     *  query 按分表规则多个Query 对象,每张表对应一个Query
     *</pre>
     */
    public static List<Query> parseQueryDataForDatabase(Query query,Class clazz)
    {

        List<Query> querysList;
        // 未设置分表规则
        ShardRule dbRule = callShardMethod(DATABASE_SHARD__METHOD_NAME,clazz);
        if (dbRule == null) {
            querysList = new ArrayList<>();
            querysList.add(query);
        } else {
            Map<String,Query> querys = new HashMap<>();

            for (Map<String,Object> data:query.getData()) {
                Map<String,Object> values = getShardData(dbRule,data);
                String shardDbSequence = dbRule.getSequence(query,values);
                String shardDbkey = buildDbkey(query.getDbkey(),shardDbSequence);
                if (!querys.containsKey(shardDbkey)) {
                    querys.put(shardDbkey,query.cloneQuery(new HashMap<String,Object>(){{
                        put(Query.DBKEY_SET,shardDbkey);
                        put(Query.DATA_SET,null);
                    }}));
                }

                querys.get(shardDbkey).setData(data);
            }

            querysList = new ArrayList(querys.values());
        }

        return querysList;
    }

    /**
     * 分表情况下解析Where
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public static List<Query> parseQueryWhereForDatabase(Query query,Class clazz)
    {
        List<Query> querysList;

        ShardRule dbRule = callShardMethod(DATABASE_SHARD__METHOD_NAME,clazz);
        // 未设置分表规则
        if (dbRule == null) {
            querysList = new ArrayList<>();
            querysList.add(query);
        } else {
            Map<String,Query> querys = new HashMap<>();
            // 分表规则where 条件值
            Map<String,Object> ruleWhere = getShardData(dbRule,query.getWhere());
            Map<String,Map<String,Object>> sequenceWhere = formatShardRuleWhere(ruleWhere,dbRule,query);

            for (Map.Entry<String,Map<String,Object>> entry : sequenceWhere.entrySet()) {
                String shardDbkey =  buildDbkey(query.getDbkey(), entry.getKey());
                if (!querys.containsKey(shardDbkey)) {
                    Map<String,Object> where = SqlMap.make();
                    where.putAll(query.getWhere());
                    where.putAll(entry.getValue());
                    querys.put(shardDbkey,query.cloneQuery(new HashMap<String,Object>(){{
                        put(Query.DBKEY_SET,shardDbkey);
                        put(Query.WHERE_SET,where);
                    }}));
                }
            }

            querysList = new ArrayList(querys.values());
        }

        return querysList;
    }


    /**
     * 获获分表,分库字段数据
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public static Map<String,Object> getShardData(ShardRule shardRule, Map<String,Object> data)
    {
        String[] fields = shardRule.getFields();
        Map<String,Object> values = new HashMap<>();
        for (String field:fields) {
            if (data.containsKey(field)) {
                values.put(field,data.get(field));
            }
        }

        return values;
    }

    /**
     * 按分区字段分离where条件
     *<B>说明：</B>
     *<pre>
     *  适用分表,分库
     *</pre>
     */
    public static Map<String,Map<String,Object>> formatShardRuleWhere(Map<String,Object> condition,ShardRule shardRule,Query query)
    {

        Map<String,Map<String,Object>> shardList = new HashMap<>();
        String shardTableSequence;
        // 如是多字段组合,则直接丢给getSequence 处理
        if (condition.size() >= 2) {
            shardTableSequence = shardRule.getSequence(query,condition);
            shardList.put(shardTableSequence,condition);
            return shardList;
        }

        String fieldName = shardRule.getFields()[0];
        Object ruleWhereValue = condition.get(fieldName);

        if (ruleWhereValue instanceof ArrayList) {
            // 格式['表达式','值'],比如['in',[1,2,3,4]]
            ArrayList values = (ArrayList)ruleWhereValue;
            String operator = (String)values.get(0);
            if (operator.equals(Builder.IN_EXP)) {
                Object columnValue = values.get(1);
                // 支持三种类型数组 long,int,string
                int valueLength = Array.getLength(columnValue);
                Map<String,ArrayList> sequenceList = new HashMap<>();
                for (int i = 0;i < valueLength;i++) {
                    Object inValue = Array.get(columnValue,i);
                    shardTableSequence = shardRule.getSequence(query,new HashMap<String,Object>(){{
                        put(fieldName,inValue);
                    }});

                    ArrayList list;
                    if (sequenceList.containsKey(shardTableSequence)) {
                        list = sequenceList.get(shardTableSequence);
                    } else {
                        list = new ArrayList();
                    }

                    list.add(Array.get(columnValue,i));
                    sequenceList.put(shardTableSequence,list);
                }

                for (Map.Entry<String, ArrayList> entry : sequenceList.entrySet()) {
                    ArrayList inVals = entry.getValue();
                    shardList.put(entry.getKey(), SqlMap.mExp(fieldName,Builder.IN_EXP,inVals.toArray()));
                }
            } else if (operator.equals(Builder.EQ_EXP)) {
                shardTableSequence = shardRule.getSequence(query,new HashMap<String,Object>(){{
                    put(fieldName,values.get(1));
                }});

                shardList.put(shardTableSequence,condition);
            }
        }

        return shardList;
    }

    /**
     * 调用分区静态方法
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static <T> T callShardMethod(String methodName,Class clazz)
    {
        try {
            // 添加缓存
            Method method = clazz.getMethod(methodName);
            return (T)method.invoke(null);
        } catch (Exception ex) {

        }

        return null;
    }

    /**
     * 构建分区真实表名
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static String buildTableName(String tableName,String shardSequence)
    {
        return tableName.replace("{table}",shardSequence);
    }

    /**
     * 构建分区真实dbkey
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static String buildDbkey(String dbkey,String shardSequence)
    {
        return dbkey.replace("{db}",shardSequence);
    }

}
